/**
    @file lua_scripted.cpp
    @brief Interface for lua-scripted objects
*/
#include "lua/lua_scripted.hpp"
#include <fstream>

pm3::LUA_SCRIPTED::LUA_SCRIPTED() {
    m_lua_state = NULL;

    m_unsaved = true;
}

pm3::LUA_SCRIPTED::~LUA_SCRIPTED() {
    LOGGER *log = LOGGER::getSingletonPtr();

    if (m_lua_state) {
        lua_close( m_lua_state );
        m_lua_state = NULL;
    }
}

void pm3::LUA_SCRIPTED::bind_basic() {
    // void log( string log_section, string log_text )
    lua_register( m_lua_state, "log", pm3::lua::log );
}

bool pm3::LUA_SCRIPTED::load( const std::string &r_script ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::string str;

    // Close old Lua state
    if (m_lua_state) {
        lua_close( m_lua_state );
        m_lua_state = NULL;
    }

    // Try to open lua
    m_lua_state = luaL_newstate();
    // Fail.?
    if (!m_lua_state) {
        if (log)
            log->report( "Lua", "E: Failed to open Lua interpretator\n" );
        return false;
    }

    // Load all bindings
    lua_pushcfunction( m_lua_state, luaopen_io );
    lua_call( m_lua_state, 0, 0);
    lua_pushcfunction( m_lua_state, luaopen_base );
    lua_call( m_lua_state, 0, 0);
    lua_pushcfunction( m_lua_state, luaopen_table );
    lua_call( m_lua_state, 0, 0);
    lua_pushcfunction( m_lua_state, luaopen_string );
    lua_call( m_lua_state, 0, 0);
    lua_pushcfunction( m_lua_state, luaopen_math );
    lua_call( m_lua_state, 0, 0);
    // Bind the basic C++ functions
    bind_basic();
    // Bind all other necessary C++ functions
    bind();

    // Attempt to load the luascript
    int ret = luaL_loadfile( m_lua_state, r_script.c_str() );
    // All ok?
    if (ret == 0) {
        // Store script path
        m_script_path = r_script;

        // Execute the script with 0 arguments and 1 return value
        if (lua_pcall ( m_lua_state, 0, 1, 0 ) == 0) {
            // Mark the script as saved
            m_unsaved = false;

            if (log)
                log->report( "Lua", "I: Script \"%s\" executed\n", r_script.c_str(), m_lua_state );
            return true;
        }

        if (log)
            log->report( "Lua", "E: Script \"%s\" execution error: \"%s\"\n", 
                         r_script.c_str(), 
                         lua_tostring( m_lua_state, lua_gettop( m_lua_state ) ) );
    } else {
        switch (ret) {
            case LUA_ERRRUN:
                str = "runtime";
                break;

            case LUA_ERRSYNTAX:
                str = "syntax";
                break;

            case LUA_ERRMEM:
                str = "memory";
                break;
        }

        if (log)
            log->report( "Lua", "E: Failed to load script file \"%s\" (%s error, %d)\n", r_script.c_str(), str.c_str(), ret );

        lua_close( m_lua_state );
        m_lua_state = NULL;
    }

    return false;
}

void pm3::LUA_SCRIPTED::update() {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::string error;
    // Lua online and worldscript loaded?
    if (m_lua_state) {
        // Push the function name
		lua_pushstring( m_lua_state, "update" );
        // Push the function as global["update"]
		lua_gettable( m_lua_state, LUA_GLOBALSINDEX );
		// Push elapsed time as the first argument
		lua_pushnumber( m_lua_state, m_timer.getMilliseconds() );
		// Call the function
		if (lua_pcall( m_lua_state, 1, 0, 0 ) != 0) {
            // Fetch the error
            error = lua_tostring( m_lua_state, lua_gettop( m_lua_state ) );
            // Don't report errors like "attempt to call a nil value"
            if (log && error != "attempt to call a nil value")
                log->report( "Lua", "E: Lua update callback error in file \"%s\": \"%s\"\n", 
                             m_script_path.c_str(), error.c_str() );
        }
    }
}

std::string pm3::LUA_SCRIPTED::get_callback_contents( const std::string &r_name ) {
    std::vector< std::string > tok;
    std::string contents;
    std::string line;
    bool in = false;

    //! \todo Implement argument name replacement
    //! \todo Add support for single line callbacks

    std::ifstream fi( m_script_path.c_str() );
    if (fi.is_open()) {
        // Read the whole file line-by-line
        while ( fi.good() ) {
            // Get a line
            std::getline( fi, line );

            // Tokenize the line
            tok = pm3::util::tokenize( line, " \t\r\n()" );

            // The contents?
            if (in) {
                // End of callback?
                if ( tok.size() >= 1 && ( tok [0].compare( "end" ) == 0 ) ) {
                    in = false;
                    // No point in searching further
                    break;
                } else
                    contents += line + "\n";
            }

            // Is it a function?
            if ( tok.size() >= 2 && ( tok [0].compare( "function" ) == 0 ) ) {
                // Is it the callback that we're looking for?
                if ( tok [1].compare( r_name ) == 0 )
                    in = true;
            }
        }
        // Close the file
        fi.close();
    }

    return contents;
}

int pm3::lua::log( lua_State *p_lua_state ) {
    LOGGER *log = LOGGER::getSingletonPtr();
    int ret = -1;

    // Make sure there is a log
    if (!log)
        return 0;

    // Make sure we have enough arguments
    int argc = lua_gettop( p_lua_state );
    // If not, then throw an error
    if (argc < 2) {
        lua_pushstring( p_lua_state, "load_scene: At least 2 arguments (log section, text) needed" );
        lua_error( p_lua_state );
    }

    // Get the section
    const char *s_section = lua_tostring( p_lua_state, 1 );
    // Get the text
    const char *s_text = lua_tostring( p_lua_state, 2 );

    if (log)
        log->report( s_section, "%s", s_text );

    // There are no results
    return 0;
}

